python 正则表达式 模式匹配与正则表达式 一
用正则表达式查找文本模式
正则表达式, 简称为 regex
, 是文本模式的描述方法。例如, \d
是一个正则表达式, 表示一位数字字符,即任何一位 0 到 9 的数字。 Python 使用正则表达式\d\d\d-\d\d\d-\d\d\d\d
, 来匹配前面 isPhoneNumber()
函数匹配的同样文本: 3 个数字、一个短横线、 3 个数字、一个短横线、 4 个数字。所有其他字符串都不能匹配\d\d\d-\d\d\d-\d\d\d\d 正则表达式。
但正则表达式可以复杂得多。例如,在一个模式后加上花括号包围的 3({3}),就是说,“匹配这个模式 3 次”。所以较短的正则表达式\d{3}-\d{3}-\d{4}, 也匹配正确的电话号码格式。
创建正则表达式对象
Python 中所有正则表达式的函数都在 re 模块中。
import re
向re.compile()
传入一个字符串值,表示正则表达式,它将返回一个 Regex 模式对象(或者就简称为 Regex 对象)。
要创建一个 Regex 对象来匹配电话号码模式,就在交互式环境中输入以下代码(, \d 表示“一个数字字符”, \d\d\d-\d\d\d-\d\d\d\d 是正确电话号码模式的正则表达式)。
phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
现在 phoneNumRegex 变量包含了一个 Regex 对象。
匹配 Regex 对象
Regex 对象的search()方法
查找==传入的字符串==, 寻找该正则表达式的所有匹配。如果字符串中没有找到该正则表达式模式, search()方法将返回 None。如果找到了该模式,search()方法将返回一个 Match 对象
。 Match 对象有一个group()方法
,它返回被查找字符串中实际匹配的文本。
>>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
>>> mo = phoneNumRegex.search('My number is 415-555-4242.')
>>> print('Phone number found: ' + mo.group())
Phone number found: 415-555-4242
将期待的模式传递给 re.compile(), 并将得到的 Regex 对象保存在phoneNumRegex 中。然后我们在 phoneNumRegex 上调用 search(), 向它传入想查找的字符串。查找的结果保存在变量 mo 中。在这个例子里, 我们知道模式会在这个字符串中找到, 所以我们知道会返回一个 Match 对象。 知道 mo 包含一个 Match 对象, 而不是空值 None,我们就可以在 mo 变量上调用 group(), 返回匹配的结果。将 mo.group()写在打印语句中, 显示出完整的匹配,即 415-555-4242。
用正则表达式匹配更多模式
利用括号分组
假定想要将区号从电话号码中分离。添加括号将在正则表达式中==创建“分组”==:(\d\d\d)-(\d\d\d-\d\d\d\d)。然后可以使用group()匹配对象方法
,从一个分组中获取匹配的文本。
正则表达式字符串中的第一对括号是第 1 组。第二对括号是第 2 组。向 group()匹配对象方法传入整数 1 或 2, 就可以取得匹配文本的不同部分。 ==向 group()方法传入 0 或不传入参数, 将返回整个匹配的文本。==
>>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)')
>>> mo = phoneNumRegex.search('My number is 415-555-4242.')
>>> mo.group(1)
'415'
>>> mo.group(2)
'555-4242'
>>> mo.group(0)
'415-555-4242'
>>> mo.group()
'415-555-4242'
如果想要一次就获取所有的分组, 请使用 groups()方法,
注意函数名的复数形式。
>>> mo.groups()
('415', '555-4242')
>>> areaCode, mainNumber = mo.groups()
>>> print(areaCode)
415
>>> print
因为== mo.groups()返回多个值的元组==, 所以你可以使用多重复制的技巧, 每个值赋给一个独立的变量, 就像前面的代码行: areaCode, mainNumber = mo.groups()。 括号在正则表达式中有特殊的含义, 但是如果你需要在文本中匹配括号, 怎么办?例如, 你要匹配的电话号码, 可能将区号放在一对括号中。在这种情况下, 就需要用倒斜杠对(和)进行字符转义。
>>> phoneNumRegex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)')
>>> mo = phoneNumRegex.search('My phone number is (415) 555-4242.')
>>> mo.group(1)
'(415)'
>>> mo.group(2)
'555-4242'
用管道匹配多个分组
字符|
称为“管道”。希望匹配许多表达式中的一个时, 就可以使用它。例如,正则表达式 r'Batman|Tina Fey'将匹配'Batman'或'Tina Fey'。
如果 Batman 和 Tina Fey 都出现在被查找的字符串中, 第一次出现的匹配文本,将作为 Match 对象返回。
>>> heroRegex = re.compile (r'Batman|Tina Fey')
>>> mo1 = heroRegex.search('Batman and Tina Fey.')
>>> mo1.group()
'Batman'
>>> mo2 = heroRegex.search('Tina Fey and Batman.')
>>> mo2.group()
'Tina Fey'
也可以使用管道来匹配多个模式中的一个, 作为正则表达式的一部分。假设你希望匹配'Batman'、 'Batmobile'、 'Batcopter'和'Batbat'中任意一个。因为所有这些字符串都以 Bat 开始, 所以如果能够只指定一次前缀, 就很方便。这可以通过括号实现。
>>> batRegex = re.compile(r'Bat(man|mobile|copter|bat)')
>>> mo = batRegex.search('Batmobile lost a wheel')
>>> mo.group()
'Batmobile'
>>> mo.group(1)
'mobile'
方法调用 mo.group()返回了完全匹配的文本'Batmobile', 而mo.group(1)
只是返回第一个括号分组内匹配的文本'mobile'。通过使用管道字符和分组括号, 可以指定几种可选的模式, 让正则表达式去匹配。
如果需要匹配真正的管道字符, 就用倒斜杠转义,即|。
命名分组
命名分组就是给具有默认分组编号的组另外再给一个别名。命名分组的语法格式如下:
(?P<name>正则表达式)
#name是一个合法的标识符
提取字符串中的ip地址:
s = "ip='230.192.168.78',version='1.0.0'"
m=re.search(r"ip='(?P<ip>\d+\.\d+\.\d+\.\d+).*", s)
print(m.group('ip'))
# 输出:230.192.168.78
用问号实现可选匹配
有时候, 想匹配的模式是可选的。就是说, 不论这段文本在不在, 正则表达式都会认为匹配。字符?
表明它前面的分组在这个模式中是可选的。
>>> batRegex = re.compile(r'Bat(wo)?man')
>>> mo1 = batRegex.search('The Adventures of Batman')
>>> mo1.group()
'Batman'
>>> mo2 = batRegex.search('The Adventures of Batwoman')
>>> mo2.group()
'Batwoman'
正则表达式中的(wo)?部分表明, 模式 wo 是可选的分组。该正则表达式匹配的文本中, wo 将出现零次或一次。这就是为什么正则表达式既匹配'Batwoman', 又匹配'Batman'。
用星号匹配零次或多次
*(称为星号)意味着“匹配零次或多次”,即星号之前的分组,可以在文本中出现任意次。它可以完全不存在,或一次又一次地重复。
>>> batRegex = re.compile(r'Bat(wo)*man')
>>> mo1 = batRegex.search('The Adventures of Batman')
>>> mo1.group()
'Batman'
>>> mo2 = batRegex.search('The Adventures of Batwoman')
>>> mo2.group()
'Batwoman'
>>> mo3 = batRegex.search('The Adventures of Batwowowowoman')
>>> mo3.group()
'Batwowowowoman'
对于'Batman', 正则表达式的(wo)部分匹配 wo 的零个实例。对于'Batwoman',(wo)匹配 wo 的一个实例。对于'Batwowowowoman', (wo)匹配 wo 的 4 个实例。如果需要匹配真正的星号字符, 就在正则表达式的星号字符前加上倒斜杠,即\。
用加号匹配一次或多次
*意味着“匹配零次或多次”, +(加号) 则意味着“匹配一次或多次”
。 星号不要求分组出现在匹配的字符串中, 但加号不同, 加号前面的分组必须“至少出现一次”
。这不是可选的。
>>> batRegex = re.compile(r'Bat(wo)+man')
>>> mo1 = batRegex.search('The Adventures of Batwoman')
>>> mo1.group()
'Batwoman'
>>> mo2 = batRegex.search('The Adventures of Batwowowowoman')
>>> mo2.group()
'Batwowowowoman'
>>> mo3 = batRegex.search('The Adventures of Batman')
>>> mo3 == None
True
正则表达式 Bat(wo)+man 不会匹配字符串'The Adventures of Batman',因为加号要求 wo 至少出现一次。
用花括号匹配特定次数
如果想要一个分组重复特定次数,就在正则表达式中该分组的后面,跟上花括号包围的数字。例如,正则表达式(Ha){3}将匹配字符串'HaHaHa',但不会匹配'HaHa',因为后者只重复了(Ha)分组两次。 除了一个数字,还可以指定一个范围,即在花括号中写下一个最小值、一个逗号和一个最大值。例如,正则表达式(Ha){3,5}将匹配'HaHaHa'、 'HaHaHaHa'和'HaHaHaHaHa'。 也可以不写花括号中的第一个或第二个数字, 不限定最小值或最大值。 (Ha){3,}将匹配 3 次或更多次实例, (Ha){,5}将匹配 0 到 5 次实例。花括号让正则表达式更简短。
>>> haRegex = re.compile(r'(Ha){3}')
>>> mo1 = haRegex.search('HaHaHa')
>>> mo1.group()
'HaHaHa'
>>> mo2 = haRegex.search('Ha')
>>> mo2 == None
True
这里, (Ha){3}匹配'HaHaHa', 但不匹配'Ha'。因为它不匹配'Ha',所以 search()返回 None。
贪心和非贪心匹配
在字符串'HaHaHaHaHa'中,因为(Ha){3,5}可以匹配 3 个、 4 个或 5 个实例,你可能会想,为什么在前面花括号的例子中, Match 对象的 group()调用会返回'HaHaHaHaHa',而不是更短的可能结果。毕竟, 'HaHaHa'和'HaHaHaHa'也能够有效地匹配正则表达式(Ha){3,5}。 Python 的正则表达式==默认是“贪心==” 的, 这表示在有二义的情况下,它们会尽可能匹配最长的字符串。==花括号的“非贪心” 版本匹配尽可能最短的字符串==,==即在结束的花括号后跟着一个问号。==
>>> greedyHaRegex = re.compile(r'(Ha){3,5}')
>>> mo1 = greedyHaRegex.search('HaHaHaHaHa')
>>> mo1.group()
'HaHaHaHaHa'
>>> nongreedyHaRegex = re.compile(r'(Ha){3,5}?')
>>> mo2 = nongreedyHaRegex.search('HaHaHaHaHa')
>>> mo2.g
请注意, 问号在正则表达式中可能有两种含义: 声明非贪心匹配或表示可选的分组。这两种含义是完全无关的。
findall()方法
除了search方法外, Regex对象也有一个findall()方法
。search()将返回一个Match对象, 包含被查找字符串中的“第一次” 匹配的文本,而 findall()方法将返回一组字符串
, 包含被查找字符串中的所有匹配。
>>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
>>> mo = phoneNumRegex.search('Cell: 415-555-9999 Work: 212-555-0000')
>>> mo.group()
'415-555-9999'
另一方面, findall()不是返回一个 Match 对象, 而是==返回一个字符串列表==, 只要在正则表达式中没有分组。列表中的每个字符串都是一段被查找的文本, 它匹配该正则表达式。
>>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') # has no groups
>>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000')
['415-555-9999', '212-555-0000']
如果在正则表达式中有分组, 那么 findall 将返回元组的列表。每个元组表示一个找到的匹配, 其中的项就是正则表达式中每个分组的匹配字符串。
>>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)') # has groups
>>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000')
[('415', '555', '1122'), ('212', '555', '0000')]
==作为 findall()方法的返回结果的总结,请记住下面两点:== 1. 如果调用在一个没有分组的正则表达式上, 例如\d\d\d-\d\d\d-\d\d\d\d, 方法findall()将返回一个匹配字符串的列表, 例如['415-555-9999', '212-555-0000']。 2. 如果调用在一个有分组的正则表达式上, 例如(\d\d\d)-(\d\d\d)-(\d\d\d\d), 方法 findall()将返回一个==字符串的元组的列表==(每个分组对应一个字符串), 例如[('415','555', '1122'), ('212', '555', '0000')]。
字符分类
在前面电话号码正则表达式的例子中, 你知道\d 可以代表任何数字。也就是说, \d是正则表达式(0|1|2|3|4|5|6|7|8|9)的缩写。有许多这样的“缩写字符分类”, ==常用字符分类的缩写代码== 字符分类对于缩短正则表达式很有用。字符分类[0-5]只匹配数字 0 到 5, 这比输入(0|1|2|3|4|5)要短很多。
例如, 在交互式环境中输入以下代码:
>>> xmasRegex = re.compile(r'\d+\s\w+')
>>> xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7
swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge')
['12 drummers', '11 pipers', '10 lords', '9 ladies', '8 maids', '7 swans', '6
geese', '5 rings', '4 birds', '3 hens', '2 doves', '1 partridge']